package mpt

import (
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/ethdb"
	"github.com/ethereum/go-ethereum/trie"
	pbcom "hundsun.com/hsl/hschain/protos/common"
	dbm "hundsun.com/hsl/hschain/store/db"
)

// KeyValueStore adapter backend
type KeyValueStore struct {
	db dbm.Database
}

// NewKeyValueStore ...
func NewKeyValueStore(db dbm.Database) *KeyValueStore {
	return &KeyValueStore{db: db}
}

// Has ...
func (kvs *KeyValueStore) Has(key []byte) (bool, error) {
	return kvs.db.Has(key)
}

// Get ...
func (kvs *KeyValueStore) Get(key []byte) ([]byte, error) {
	return kvs.db.Get(key)
}

// Put ...
func (kvs *KeyValueStore) Put(key []byte, value []byte) error {
	return kvs.db.Set(key, value)
}

// Delete ...
func (kvs *KeyValueStore) Delete(key []byte) error {
	return kvs.db.Delete(key)
}

// NewBatch new batch
func (kvs *KeyValueStore) NewBatch() ethdb.Batch {
	bch := &batch{
		kvs.db.NewBatch(true),
	}
	return bch
}

// NewIterator ...
func (kvs *KeyValueStore) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
	return kvs.db.NewIteratorWithRange(prefix, start)
}

// Stat no need implement
func (kvs *KeyValueStore) Stat(property string) (string, error) {
	return "", nil
}

// Compact no need implement
func (kvs *KeyValueStore) Compact(start []byte, limit []byte) error {
	return nil
}

// Close no need implement
func (kvs *KeyValueStore) Close() error {
	return nil
}

type batch struct {
	dbm.Batch
}

// Put batch put
func (b *batch) Put(key []byte, value []byte) error {
	return b.Set(key, value)
}

// Replay no need implement
func (b *batch) Replay(w ethdb.KeyValueWriter) error {
	return nil
}

// TrieWrap trie wrap cotains trie database and trie tree
type TrieWrap struct {
	*trie.Database
	*trie.Trie
	info *pbcom.StateInfo
}

// NewTrieWrap ...
func NewTrieWrap(db dbm.Database, hash []byte) (*TrieWrap, error) {
	trieDB := trie.NewDatabase(NewKeyValueStore(db))
	tree, err := trie.New(common.BytesToHash(hash), trieDB)
	if err != nil {
		return nil, err
	}
	return &TrieWrap{Database: trieDB, Trie: tree}, nil
}

// Get get data from trie
func (tr *TrieWrap) Get(key []byte) ([]byte, error) {
	return tr.Trie.TryGet(key)
}

// Update updata data to trie
func (tr *TrieWrap) Update(key, value []byte) error {
	return tr.Trie.TryUpdate(key, value)
}

// Delete ...
func (tr *TrieWrap) Delete(key []byte) error {
	return tr.Trie.TryDelete(key)
}

// Commit commit trie node to memory and calculate new state root
func (tr *TrieWrap) Commit() (root []byte, err error) {
	hash, err := tr.Trie.Commit(nil)
	return hash.Bytes(), err
}

// Commit2DB commit trie node to db
func (tr *TrieWrap) Commit2DB(hash []byte) error {
	return tr.Database.Commit(common.BytesToHash(hash), true, nil)
}

// SetStateInfo set state info
func (tr *TrieWrap) SetStateInfo(info *pbcom.StateInfo) {
	tr.info = info
}

// GetStateInfo get state info
func (tr *TrieWrap) GetStateInfo() *pbcom.StateInfo {
	return tr.info
}
